﻿using System;
using System.Collections;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using Dapper;
using NiuX.Dapper.Extensions;
using NiuX.Dapper.Quotes;

namespace NiuX.Dapper.Visitors;

public class WhereVisitor : ExpressionVisitor
{

    public WhereVisitor()
    {
    }


    public StringBuilder ConditionSql = new();

    public ISqlQuote SqlQuote { get; set; }


    public string ResolveExpression<T>(Expression<Func<T, bool>> exp)
    {
        Visit((exp).Body);
        return ConditionSql.ToString();
    }

    protected override Expression VisitBinary(BinaryExpression node)
    {
        ConditionSql.Append("(");
        Visit(node.Left);

        ConditionSql.Append($" {node.NodeType.TransferExpressionType()} ");

        Visit(node.Right);
        ConditionSql.Append(")");

        return node;
    }

    protected override Expression VisitConstant(ConstantExpression node)
    {
        if (node.Type == typeof(string))
        {
            ConditionSql.Append($"'{node.Value}'");
        }
        else
        {
            ConditionSql.Append(node.Value);
        }

        return node;
    }

    private int ParameterCount;
    private string _tempFieldName;

    private string TempFieldName
    {
        get => _tempFieldName + ParameterCount;
        set => _tempFieldName = value;
    }
    protected override Expression VisitMember(MemberExpression node)
    {
        ConditionSql.Append($"{SqlQuote.OpenQuote}{node.Member.GetCustomAttribute<ColumnAttribute>()?.Name ?? node.Member.Name}{SqlQuote.CloseQuote}");
        TempFieldName = node.Member.Name;
        ParameterCount++;
        return node;
    }

    #region 访问方法表达式
    /// <inheritdoc />
    /// <summary>
    /// 访问方法表达式
    /// </summary>
    /// <param name="node"></param>
    /// <returns></returns>
    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
        if (node.Method.Name == "Contains" && typeof(IEnumerable).IsAssignableFrom(node.Method.DeclaringType) &&
            node.Method.DeclaringType != typeof(string))
            In(node);
        else if (node.Method.Name == "Equals")
            Equal(node);
        else
            Like(node);

        return node;
    }

    #endregion

    private void Equal(MethodCallExpression node)
    {
        Visit(node.Object);
        ConditionSql.AppendFormat(" ={0}", ParamName);
        var argumentExpression = node.Arguments[0].ToConvertAndGetValue();
        Param.Add(TempFieldName, argumentExpression);
    }

    private void Like(MethodCallExpression node)
    {
        Visit(node.Object);
        ConditionSql.AppendFormat(" LIKE {0}", ParamName);
        switch (node.Method.Name)
        {
            case "StartsWith":
                {
                    var argumentExpression = (ConstantExpression)node.Arguments[0];
                    Param.Add(TempFieldName, argumentExpression.Value + "%");
                }
                break;
            case "EndsWith":
                {
                    var argumentExpression = (ConstantExpression)node.Arguments[0];
                    Param.Add(TempFieldName, "%" + argumentExpression.Value);
                }
                break;
            case "Contains":
                {
                    var argumentExpression = (ConstantExpression)node.Arguments[0];
                    Param.Add(TempFieldName, "%" + argumentExpression.Value + "%");
                }
                break;
            default:
                throw new DapperNoSupportException("the expression is no support this function");
        }
    }

    private void In(MethodCallExpression node)
    {
        if (node.Object is ConstantExpression constantExpression)
        {
            var arrayValue = (IList)constantExpression.Value;
            if (arrayValue.Count == 0)
            {
                ConditionSql.Append(" 1 = 2");
                return;
            }
        }

        Visit(node.Arguments[0]);

        var memberExpression = (MemberExpression)node.Object;

        var @object =
          ((ConstantExpression)(memberExpression.Expression)).Value; //这个是重点

        object value = null;

        if (memberExpression.Member.MemberType == MemberTypes.Field)
        {
            value = ((FieldInfo)memberExpression.Member).GetValue(@object);
        }
        else if (memberExpression.Member.MemberType == MemberTypes.Property)
        {
            value = ((PropertyInfo)memberExpression.Member).GetValue(@object);
        }


        ConditionSql.AppendFormat(" IN {0}", ParamName);
        Param.Add(TempFieldName, value);
    }

    public DynamicParameters Param { get; } = new();


    private string ParamName => SqlQuote.ParameterPrefix + TempFieldName;

}